package com.devinsor.spring.framework.context;

import com.devinsor.spring.framework.annotation.Autowried;
import com.devinsor.spring.framework.annotation.Controller;
import com.devinsor.spring.framework.annotation.Service;
import com.devinsor.spring.framework.aop.JdkDynamicAopProxy;
import com.devinsor.spring.framework.aop.config.AopConfig;
import com.devinsor.spring.framework.aop.support.AdvisedSupport;
import com.devinsor.spring.framework.beans.BeanWrapper;
import com.devinsor.spring.framework.beans.config.BeanDefinition;
import com.devinsor.spring.framework.beans.support.BeanDefinitionReader;

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

/**
 * @program: com.elvis.demo-v1
 * @description: IoC注入入口
 * @author: JackRen
 * @create: 2020-05-13 14:35
 **/
public class ApplicationContext {

    private String [] configLocations;
    private BeanDefinitionReader reader;

    private final Map<String,BeanDefinition> beanDefinitionMap = new HashMap<String,BeanDefinition>();

    private Map<String,BeanWrapper> factoryBeanInstanceCache = new HashMap<String, BeanWrapper>();

    private Map<String,Object> factoryBeanObjectCache = new HashMap<String, Object>();

    public ApplicationContext(String... configLocations) {
       try {
           this.configLocations = configLocations;

           //1.读取配置文件
           reader = new BeanDefinitionReader(configLocations);

           //2.解析配置文件，将配置信息编程BeanDefinition对象
           List<BeanDefinition> beanDefinitions = reader.loadBeanDefinitions();

           //3.把BeanDefinetion对应的实例注册到beanDefinitionMap key=beanName value=beanDefinition对象
           doRegisterBeandefinition(beanDefinitions);


           //4.完成以来注入，什么时候注入？调用getBean()
           doAutoWried();
       }catch (Exception e){
           e.printStackTrace();
       }
    }

    private void doAutoWried() {
        //调用getBean()
        for (Map.Entry<String,BeanDefinition> beanDefinitionEntry : this.beanDefinitionMap.entrySet()) {
            String beanName = beanDefinitionEntry.getKey();
            //真正的注入要到getBean方法中去完成
            //getBean方法要干两件事：
            // 第一件事创建实例，第二件事依赖注入
            getBean(beanName);
        }
    }

    public Object getBean(Class beanClass){
        return getBean(beanClass.getName());
    }

    public Object getBean(String beanName) {
        //1、获取BeanDefinition配置信息
        BeanDefinition beanDefinition = this.beanDefinitionMap.get(beanName);

        //2、用反射实例化
        Object instance =instantiateBean(beanName,beanDefinition);

        //3、将创建出来的实例包装为BeanWrapper对象
        BeanWrapper beanWrapper = new BeanWrapper(instance);

        //4、把BeanWrapper对象存入到真正的IoC容器中
        this.factoryBeanInstanceCache.put(beanName,beanWrapper);

        //5、执行依赖注入
        populateBean(beanName,beanDefinition,beanWrapper);
        return this.factoryBeanInstanceCache.get(beanName).getWrapperInstance();
    }

    /**
      * @author JackRen
     * 完成依赖注入
      * @date 2020/5/13
    */
    private void populateBean(String beanName, BeanDefinition beanDefinition, BeanWrapper beanWrapper) {
        Object instance = beanWrapper.getWrapperInstance();
        Class<?> clazz = beanWrapper.getWrapperClass();

        //只有加了注解的才进行依赖注入
        //@Component
        if(!(clazz.isAnnotationPresent(Controller.class) || clazz.isAnnotationPresent(Service.class))){
            return;
        }

        //拿到IoC容器所有实例的字段（属性）
        //private protected default public
        //OOP 只能拿到public属性
        Field[] fields = clazz.getDeclaredFields();

        for (Field field : fields) {
            if (!field.isAnnotationPresent(Autowried.class)){continue;}
            Autowried autowried = field.getAnnotation(Autowried.class);
            String autowriedBeanName = autowried.value().trim();
            if ("".equals(autowriedBeanName)){
                autowriedBeanName = field.getType().getName();
            }
            //暴力访问
            field.setAccessible(true);
            try {
                //field相当于 @Autowired private IDemoService demoService;
                //entry.getValue()相当于DemoAction的实例
                //ioc.get(beanName)相当于从IoC容器中去拿到key为demo.service.IDemoService 对应的实例
                // 即DemoService的实例
                if(this.factoryBeanInstanceCache.get(autowriedBeanName) == null){
                    continue;
                }
                field.set(instance,this.factoryBeanInstanceCache.get(autowriedBeanName).getWrapperInstance());
            } catch (IllegalAccessException e) {
                e.printStackTrace();
                continue;
            }
        }

    }

    /**
      * @author JackRen
     * 创建实例化对象
      * @date 2020/5/13
    */
    private Object instantiateBean(String beanName,BeanDefinition beanDefinition) {
        String className = beanDefinition.getBeanClassName();
        Object instance=null;
        try {
            Class<?> clazz = Class.forName(className);
            instance = clazz.newInstance();

            //1、读取配置，将通知和目标类建立关系
            AdvisedSupport config = instantionAopConfig(beanDefinition);
            config.setTargetClass(clazz);
            config.setTarget(instance);
            //判断，要不要生成代理类
            if(config.pointCutMatch()){
                instance = new JdkDynamicAopProxy(config).getProxy();
            }

            //Spring内部的容器不止一个
            factoryBeanObjectCache.put(beanName,instance);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return  instance;
    }

    private AdvisedSupport instantionAopConfig(BeanDefinition beanDefinition) {
        AopConfig config = new AopConfig();
        config.setPointCut(this.reader.getConfig().getProperty("pointCut"));
        config.setAspectClass(this.reader.getConfig().getProperty("aspectClass"));
        config.setAspectBefore(this.reader.getConfig().getProperty("aspectBefore"));
        config.setAspectAfter(this.reader.getConfig().getProperty("aspectAfter"));
        config.setAspectAfterThrow(this.reader.getConfig().getProperty("aspectAfterThrow"));
        config.setAspectAfterThrowingName(this.reader.getConfig().getProperty("aspectAfterThrowingName"));
        return new AdvisedSupport(config);
    }

    private void doRegisterBeandefinition(List<BeanDefinition> beanDefinitions) throws Exception{
        for (BeanDefinition beanDefinition:beanDefinitions){
            if(this.beanDefinitionMap.containsKey(beanDefinition.getFactoryBeanName())){
                throw new Exception("The " + beanDefinition.getFactoryBeanName() + " is exists!");
            }
            this.beanDefinitionMap.put(beanDefinition.getFactoryBeanName(),beanDefinition);
            this.beanDefinitionMap.put(beanDefinition.getBeanClassName(),beanDefinition);
        }
    }

    public int getBeanDefinitionCount() {
        return this.beanDefinitionMap.size();
    }

    public String[] getBeanDefinitionNames() {
        return this.beanDefinitionMap.keySet().toArray(new String[this.beanDefinitionMap.size()]);
    }

    public Properties getConfig() {
        return this.reader.getConfig();
    }
}
